#include "Shader.h"

using namespace Graphics;
RTTI_DEFINITIONS(Shader)

#ifdef DX_11
#include <fstream> 
#include <d3dx11async.h>

Shader::Shader(const std::string& instanceName, const std::string& filePath)
	:Engine::NResource(instanceName,filePath),
	 mVertexShader(NULL),
	 mPixelShader(NULL),
	 mLayout(NULL),
	 mMatrixBuffer(NULL),
	 mSampleState(NULL),
	 mVertexShaderBuffer(NULL),
	 mPixelShaderBuffer(NULL)
{
}

Shader::~Shader(void)
{
	Deinitialize();
}

bool Shader::Initialize()
{
	return false;
}

void Shader::Deinitialize()
{
	DeinitializeShader();
}

bool Shader::InitializeShader(D3DDevice* device,  HWND winHandle, const char* vertexShaderName, const char*  pixelShaderName)
{
	HRESULT result;
	ID3D10Blob* errorMessage;
	
	// Compile the vertex shader code.
	result = D3DX11CompileFromFile(vertexShaderName, NULL, NULL, "VShader", "vs_5_0", D3D10_SHADER_DEBUG, 0, NULL, 
				       &mVertexShaderBuffer, &errorMessage, NULL);
	if(FAILED(result))
	{
		// If the shader failed to compile it should have writen something to the error message.
		if(errorMessage)
		{
			OutputShaderErrorMessage(errorMessage, winHandle, vertexShaderName);
		}
		// If there was nothing in the error message then it simply could not find the shader file itself.
		else
		{
			MessageBox(winHandle, vertexShaderName, "Missing Shader File", MB_OK);
		}

		return false;
	}


	// Compile the pixel shader code.
	result = D3DX11CompileFromFile(pixelShaderName, NULL, NULL, "PShader", "ps_5_0", D3D10_SHADER_DEBUG, 0, NULL, 
				       &mPixelShaderBuffer, &errorMessage, NULL);
	if(FAILED(result))
	{
		// If the shader failed to compile it should have writen something to the error message.
		if(errorMessage)
		{
			OutputShaderErrorMessage(errorMessage, winHandle, pixelShaderName);
		}
		// If there was  nothing in the error message then it simply could not find the file itself.
		else
		{
			MessageBox(winHandle, pixelShaderName, "Missing Shader File", MB_OK);
		}

		return false;
	}

	// Create the vertex shader from the buffer.
	result = device->GetDevice()->CreateVertexShader(mVertexShaderBuffer->GetBufferPointer(), mVertexShaderBuffer->GetBufferSize(), NULL, &mVertexShader);
	if(FAILED(result))
	{
		return false;
	}

	// Create the pixel shader from the buffer.
	result = device->GetDevice()->CreatePixelShader(mPixelShaderBuffer->GetBufferPointer(), mPixelShaderBuffer->GetBufferSize(), NULL, &mPixelShader);
	if(FAILED(result))
	{
		return false;
	}

	// Setup the description of the dynamic matrix constant buffer that is in the vertex shader.
	D3D11_BUFFER_DESC matrixBufferDesc;
	matrixBufferDesc.Usage = D3D11_USAGE_DYNAMIC;
	matrixBufferDesc.ByteWidth = sizeof(MatrixBufferType);
	matrixBufferDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
	matrixBufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
	matrixBufferDesc.MiscFlags = 0;
	matrixBufferDesc.StructureByteStride = 0;

	// Create the constant buffer pointer so we can access the vertex shader constant buffer from within this class.
	result = device->GetDevice()->CreateBuffer(&matrixBufferDesc, NULL, &mMatrixBuffer);
	CHECK(result);

	return true;
}

void Shader::DeinitializeShader()
{
	if(mSampleState)
	{
		mSampleState->Release();
		mSampleState = NULL;
	}

	if(mVertexShaderBuffer)
	{
		mVertexShaderBuffer->Release();
		mVertexShaderBuffer = NULL;
	}

	if(mPixelShaderBuffer)
	{
		mPixelShaderBuffer->Release();
		mPixelShaderBuffer = NULL;
	}

	// Release the matrix constant buffer.
	if(mMatrixBuffer)
	{
		mMatrixBuffer->Release();
		mMatrixBuffer = NULL;
	}

	// Release the layout.
	if(mLayout)
	{
		mLayout->Release();
		mLayout = NULL;
	}

	// Release the pixel shader.
	if(mPixelShader)
	{
		mPixelShader->Release();
		mPixelShader = NULL;
	}

	// Release the vertex shader.
	if(mVertexShader)
	{
		mVertexShader->Release();
		mVertexShader = NULL;
	}
}

bool Shader::SetShaderParameters(D3DDevice* device, D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix, D3DXMATRIX projectionMatrix)
 {
	 // Transpose the matrices to prepare them for the shader.
	D3DXMatrixTranspose(&worldMatrix, &worldMatrix);
	D3DXMatrixTranspose(&viewMatrix, &viewMatrix);
	D3DXMatrixTranspose(&projectionMatrix, &projectionMatrix);
 
	// Lock the constant buffer so it can be written to.
	D3D11_MAPPED_SUBRESOURCE mappedResource;
	HRESULT result = device->GetDeviceContext()->Map(mMatrixBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource);
	if(FAILED(result))
	{
		return false;
	}

	// Get a pointer to the data in the constant buffer.
	MatrixBufferType* dataPtr = (MatrixBufferType*)mappedResource.pData;

	// Copy the matrices into the constant buffer.
	dataPtr->World = worldMatrix;
	dataPtr->View = viewMatrix;
	dataPtr->Projection = projectionMatrix;

	// Unlock the constant buffer.
	device->GetDeviceContext()->Unmap(mMatrixBuffer, 0);

	// Set the position of the constant buffer in the vertex shader.
	unsigned int bufferNumber = 0;

	// Finanly set the constant buffer in the vertex shader with the updated values.
	device->GetDeviceContext()->VSSetConstantBuffers(bufferNumber, 1, &mMatrixBuffer);
	return true;
 }

bool Shader::RenderShader(D3DDevice* device, int indexCount)
{
	// Set the vertex input layout.
	device->GetDeviceContext()->IASetInputLayout(mLayout);

	// Set the vertex and pixel shaders that will be used to render this triangle.
	device->GetDeviceContext()->VSSetShader(mVertexShader, NULL, 0);
	device->GetDeviceContext()->PSSetShader(mPixelShader, NULL, 0);

	// Set the sampler state in the pixel shader.
	if(mSampleState)
	{
		device->GetDeviceContext()->PSSetSamplers(0, 1, &mSampleState);
	}

	// Render the triangle.
	device->GetDeviceContext()->DrawIndexed(indexCount, 0, 0);

	return true;
}

void Shader::OutputShaderErrorMessage(ID3D10Blob* errorMessage, HWND hwnd, const char* shaderFilename)
{
	char* compileErrors;
	unsigned long bufferSize, i;
	std::ofstream fout;

	// Get a pointer to the error message text buffer.
	compileErrors = (char*)(errorMessage->GetBufferPointer());

	// Get the length of the message.
	bufferSize = errorMessage->GetBufferSize();

	// Open a file to write the error message to.
	fout.open("shader-error.txt");

	// Write out the error message.
	for(i=0; i<bufferSize; i++)
	{
		fout << compileErrors[i];
	}

	// Close the file.
	fout.close();

	// Release the error message.
	errorMessage->Release();
	errorMessage = 0;

	// Pop a message up on the screen to notify the user to check the text file for compile errors.
	MessageBox(hwnd, "Error compiling shader.  Check shader-error.txt for message.", shaderFilename, MB_OK);

	return;
}

#endif
#ifdef DX_9
#include "Material.h"

Shader::Shader(const std::string& instanceName, const std::string& filePath)
	:NResource(instanceName,filePath),
	 mPassCount(0),
	 mEffect(NULL)
{
}

Shader::~Shader(void)
{
}

void Shader::Initialize()
{
	if(mEffect == NULL)
	{
		Load(mFilePath.c_str());
		InitializeTechniques();
	}
}

void Shader::Begin()
{
	if(FAILED(mEffect->SetTechnique(mCurrentTechnique)))	//set technique to current
	{
		return;
		//throw RendException("Shader::Begin::FAILED",__FILE__,__LINE__);
	}
	mEffect->Begin(&mPassCount,0);
}

void Shader::End()
{
	mEffect->End();
}

void Shader::InitializeTechniques()
{
	if(mEffect == NULL)
	{
		return;
	}

	D3DXHANDLE technique;
	technique = mEffect->GetCurrentTechnique();
	mCurrentTechnique = technique;

	D3DXTECHNIQUE_DESC techniqueDesc;
	mEffect->GetTechniqueDesc(technique,&techniqueDesc);
	mCurrentTechDescription = techniqueDesc;
	mPassCount = techniqueDesc.Passes;
}

void Shader::Load(const char* fileName)
{
	D3DDevice* device = GetPlatform()->GetDevice()->As<D3DDevice>();

	HRESULT result;
	result = D3DXCreateEffectFromFile(	device->GetDevice(), 
										fileName, 
										NULL, 
										NULL, 
										0, 
										NULL, 
										&mEffect,
										NULL);
	CHECK(result);
}

void Shader::SetCurrentTechnique(D3DXHANDLE technique)
{
	/*TechniqueList::iterator it = mTechniques.find(technique.c_str());
	if(it == mTechniques.end())
	{
		throw GameException("Effect::SetCurrentTechnique::Invalid Technique");
	}

	mEffect->SetTechnique(technique.c_str());
	mPassCount = mTechniques[technique.c_str()]->Passes;
	mCurrentTechnique = technique.c_str();*/
}

ShaderParameter::ShaderParameter()
	:mMaterial(NULL)
{
}

ShaderParameter::ShaderParameter(Material* material, D3DXHANDLE handle, D3DXPARAMETER_DESC description)
	:mMaterial(material),mHandle(handle),mDescription(description)
{
}

ShaderParameter::ShaderParameter(Material* material, D3DXHANDLE handle, D3DXPARAMETER_DESC description, void* value)
	:mMaterial(material),mHandle(handle),mDescription(description),mValue(value)
{
}

ShaderParameter::ShaderParameter(Material* material, D3DXHANDLE handle, D3DXPARAMETER_DESC description, void* value, UINT count)
	:mMaterial(material),mHandle(handle),mDescription(description),mValue(value),mCount(count)
{
}

ShaderParameter::~ShaderParameter()
{
}

ShaderParameter& ShaderParameter::operator<<(const D3DXMATRIX* value)
{
	mMaterial->SetParameter(this,(void*)value);
	return *this;
}

ShaderParameter& ShaderParameter::operator<<(const D3DXVECTOR2* value)
{
	mMaterial->SetParameter(this,(void*)value);
	return *this;
}

ShaderParameter& ShaderParameter::operator<<(const D3DXVECTOR3* value)
{
	mMaterial->SetParameter(this,(void*)value);
	return *this;
}

ShaderParameter& ShaderParameter::operator<<(const D3DXVECTOR4* value)
{
	mMaterial->SetParameter(this,(void*)value);
	return *this;
}

ShaderParameter& ShaderParameter::operator<<(D3DXMATRIX* value)
{
	mMaterial->SetParameter(this,(void*)value);
	return *this;
}

ShaderParameter& ShaderParameter::operator<<(D3DXVECTOR2* value)
{
	mMaterial->SetParameter(this,(void*)value);
	return *this;
}

ShaderParameter& ShaderParameter::operator<<(D3DXVECTOR3* value)
{
	mMaterial->SetParameter(this,(void*)value);
	return *this;
}

ShaderParameter& ShaderParameter::operator<<(D3DXVECTOR4* value)
{
	mMaterial->SetParameter(this,(void*)value);
	return *this;
}

ShaderParameter& ShaderParameter::operator<<(FLOAT* value)
{
	mMaterial->SetParameter(this,(void*)value);
	return *this;
}

ShaderParameter& ShaderParameter::operator<<(INT* value)
{
	mMaterial->SetParameter(this,(void*)value);
	return *this;
}

ShaderParameter& ShaderParameter::operator<<(LPCSTR value)
{
	mMaterial->SetParameter(this,(void*)value);
	return *this;
}

ShaderParameter& ShaderParameter::operator<<(LPDIRECT3DBASETEXTURE9 value)
{
	mMaterial->SetParameter(this,(void*)value);
	return *this;
}

const D3DXPARAMETER_DESC& ShaderParameter::GetDescription()
{
	return mDescription;
}

D3DXHANDLE ShaderParameter::GetHandle()
{
	return mHandle;
}

void ShaderParameter::SetValue(void* value)
{
	mValue = value;
}

void* ShaderParameter::GetValue()
{
	return mValue;
}


#endif